package org.magicsoft.office.excel.impt;

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Date;
import java.util.List;

import org.docx4j.openpackaging.parts.SpreadsheetML.WorksheetPart;
import org.magicsoft.office.db.DatabaseType;
import org.magicsoft.office.db.SQLType;
import org.magicsoft.office.docx4j.ExcelUtils;
import org.magicsoft.office.excel.model.Column;
import org.magicsoft.office.excel.model.DefaultValue;
import org.magicsoft.office.excel.model.Reference;
import org.magicsoft.office.excel.model.Sheet;
import org.magicsoft.office.excel.model.TargetTable;
import org.magicsoft.office.exception.DBException;
import org.magicsoft.office.exception.ModelException;
import org.magicsoft.office.exception.UnsupportedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xlsx4j.sml.Cell;
import org.xlsx4j.sml.Row;

public class TempTableDataWriter {

	private static final Logger logger = LoggerFactory.getLogger(TempTableDataWriter.class);

	public void write(Sheet sheet, Connection conn, int sheetIndex, int batchSize, ExcelUtils excelUtils,
			String currentInsertSQL, DatabaseType databaseType) {
		PreparedStatement stmt = null;
		try {
			stmt = conn.prepareStatement(currentInsertSQL);

			WorksheetPart worksheetPart;
			if (sheet.getSheetName() != null) {
				worksheetPart = excelUtils.getWorksheet(sheet.getSheetName());
				if (worksheetPart == null) {
					throw new ModelException("期望进行导入的sheet：“" + sheet.getSheetName() + "”未找到。");
				}
			} else {
				worksheetPart = excelUtils.getWorksheet(sheetIndex);
			}
			List<Row> rows = excelUtils.getRows(worksheetPart);

			int rowIndex = 0;
			for (Row row : rows) {
				if (rowIndex++ == 0) {
					continue;
				}

				this.writeRow(sheet, excelUtils, stmt, row, rowIndex, batchSize, databaseType);
			}
			stmt.executeBatch();
		} catch (SQLException e) {
			throw new DBException(e);
		} finally {
			if (stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					logger.error(e.getMessage(), e);
				}
			}
		}
	}

	private void writeRow(Sheet sheet, ExcelUtils excelUtils, PreparedStatement stmt, Row row, int rowIndex,
			int batchSize, DatabaseType databaseType) {
		try {
			Column prevColumn;
			String cellValue;
			Column column;
			int count = 0;

			prevColumn = null;
			List<Cell> cells = excelUtils.getCells(row);
			for (Cell cell : cells) {
				// 根据cell的位置（例如：A2），得到对应的列模型。
				column = sheet.getColumn(cell.getR());
				if (column == null) {
					// column为空说明在模型没有定义当前列，不需要进行导入。
					continue;
				}

				// 这里需要指出的是，docx4j的cell集合中不包括为空的单元格。
				// 比如A,B,C,D四列我们都需要导入数据，但B,C在excel文件中是空白单元格，那么，docx4j的cell集合中是不包括B,C的。
				// 循环完A之后，直接就是D，这时我们需要将B,C对应的字段设置为空。
				while (column.getPrev() != prevColumn) {
					if (prevColumn == null) {
						prevColumn = sheet.getFirstColumn();
					} else {
						prevColumn = prevColumn.getNext();
					}

					if (DatabaseType.ORACLE.equals(databaseType)
							&& prevColumn.getSqlType().getJdbcType() == Types.BOOLEAN) {
						stmt.setBoolean(prevColumn.getParameterIndex(), false);
					} else {
						stmt.setNull(prevColumn.getParameterIndex(), prevColumn.getSqlType().getJdbcType());
					}
				}
				prevColumn = column;

				// 获取单元格的值，如果为空setNull，否则根据列模型中的SQL类型进行赋值。
				// 虽然按照上面的逻辑此处不会为空，但实际情况中却出现过cellValue为空的情况（确实是一个空白的单元格，有可能因为包含格式，所以保留了这个实例）。
				cellValue = excelUtils.getCellValue(cell);
				if (cellValue != null) {
					cellValue = cellValue.trim();
				}
				if (cellValue == null || "".equals(cellValue)) {
					stmt.setNull(column.getParameterIndex(), column.getSqlType().getJdbcType());
				} else {
					this.setValue(column.getSqlType(), column.getParameterIndex(), stmt, cellValue);
				}
			}

			// 这里同上面一样，进行空值的补全。只不过这里是从最后一列开始向前迭代。
			column = sheet.getLastColumn();
			while (column != prevColumn) {
				stmt.setNull(column.getParameterIndex(), column.getSqlType().getJdbcType());
				column = column.getPrev();
			}

			this.writeAssistantColumns(sheet, stmt, rowIndex);

			// 批量提交。
			stmt.addBatch();
			if (++count % batchSize == 0) {
				stmt.executeBatch();
			}
		} catch (SQLException e) {
			throw new DBException(e);
		}
	}

	private void writeAssistantColumns(Sheet sheet, PreparedStatement stmt, int rowIndex) throws SQLException {
		// 引用的外键ID列补全。
		for (Reference refColumn : sheet.getReferences()) {
			stmt.setNull(refColumn.getParameterIndex(), refColumn.getRefPrimaryKey().getSqlType().getJdbcType());
		}
		// defaultValue, OID, NID补全。
		for (TargetTable targetTable : sheet.getTargetTables()) {
			if (targetTable.getDefaultValues() != null) {
				for (DefaultValue defaultValue : targetTable.getDefaultValues()) {
					this.setValue(defaultValue.getSqlType(), defaultValue.getParameterIndex(), stmt,
							defaultValue.getValue());
				}
			}

			if (targetTable.getPrimaryKey() != null) {
				stmt.setNull(targetTable.getOidParameterIndex(),
						targetTable.getPrimaryKey().getSqlType().getJdbcType());

				if (targetTable.getPrimaryKey().getIdGenerator() != null) {
					switch (targetTable.getPrimaryKey().getSqlType()) {
					case BIGINT:
						stmt.setLong(targetTable.getNidParameterIndex(),
								(long) targetTable.getPrimaryKey().getIdGenerator().generate());
						break;
					case CHAR:
					case VARCHAR:
						stmt.setString(targetTable.getNidParameterIndex(),
								(String) targetTable.getPrimaryKey().getIdGenerator().generate());
						break;
					default:
						throw new UnsupportedException();
					}
				} else {
					// 如果没有指定ID生成器，则认为使用数据库原生的自增主键，此时不需要声明nid_（new id -
					// 新记录的ID）。
					stmt.setNull(targetTable.getNidParameterIndex(),
							targetTable.getPrimaryKey().getSqlType().getJdbcType());
				}
			}
		}
		stmt.setInt(sheet.getExcelRowParameterIndex(), rowIndex);
		stmt.setNull(sheet.getErrorMsgParameterIndex(), SQLType.CLOB.getJdbcType());
	}

	/**
	 * 向预编译Statement中设置参数值。
	 * 
	 * @param sqlType
	 *            字段对应的SQL类型。
	 * @param parameterIndex
	 *            jdbc参数索引。
	 * @param stmt
	 *            预编译Statement。
	 * @param cellValue
	 *            Excel单元格的值。
	 * @throws SQLException
	 *             jdbc异常。
	 */
	private void setValue(SQLType sqlType, int parameterIndex, PreparedStatement stmt, Object cellValue)
			throws SQLException {
		switch (sqlType) {
		case BIT:
		case BOOLEAN:
			this.setBooleanParameter(cellValue, stmt, parameterIndex);
			break;
		case CHAR:
		case VARCHAR:
		case CLOB:
		case LONGNVARCHAR:
		case LONGVARBINARY:
		case LONGVARCHAR:
		case NCHAR:
		case NCLOB:
		case NVARCHAR:
			stmt.setString(parameterIndex, String.valueOf(cellValue));
			break;
		case SMALLINT:
		case TINYINT:
			this.setShortParameter(cellValue, stmt, parameterIndex);
			break;
		case INTEGER:
			this.setIntParameter(cellValue, stmt, parameterIndex);
			break;
		case BIGINT:
			this.setLongParameter(cellValue, stmt, parameterIndex);
			break;
		case DATE:
			this.setDateParameter(cellValue, stmt, parameterIndex);
			break;
		case DECIMAL:
			this.setBigDecimalParameter(cellValue, stmt, parameterIndex);
			break;
		case DOUBLE:
			this.setDoubleParameter(cellValue, stmt, parameterIndex);
			break;
		case FLOAT:
		case REAL:
			this.setFloatParameter(cellValue, stmt, parameterIndex);
			break;
		default:
			throw new UnsupportedException();
		}
	}

	/**
	 * 设置Boolean类型的参数。
	 * 
	 * @param cellValue
	 *            Excel单元格值。
	 * @param stmt
	 *            预编译Statement。
	 * @param parameterIndex
	 *            jdbc参数索引。
	 * @throws SQLException
	 *             jdbc异常。
	 */
	private void setBooleanParameter(Object cellValue, PreparedStatement stmt, int parameterIndex) throws SQLException {
		if (cellValue instanceof Boolean) {
			stmt.setBoolean(parameterIndex, (boolean) cellValue);
		} else {
			String cellValueStr = String.valueOf(cellValue);
			stmt.setBoolean(parameterIndex, ("是".equals(cellValueStr) || "true".equalsIgnoreCase(cellValueStr)
					|| "yes".equalsIgnoreCase(cellValueStr) || "1".equals(cellValueStr)) ? true : false);
		}
	}

	/**
	 * 设置Short类型的参数。
	 * 
	 * @param cellValue
	 *            Excel单元格值。
	 * @param stmt
	 *            预编译Statement。
	 * @param parameterIndex
	 *            jdbc参数索引。
	 * @throws SQLException
	 *             jdbc异常。
	 */
	private void setShortParameter(Object cellValue, PreparedStatement stmt, int parameterIndex) throws SQLException {
		if (cellValue instanceof Short) {
			stmt.setShort(parameterIndex, (short) cellValue);
		} else {
			stmt.setShort(parameterIndex, Short.parseShort((String) cellValue));
		}
	}

	/**
	 * 设置Integer类型的参数。
	 * 
	 * @param cellValue
	 *            Excel单元格值。
	 * @param stmt
	 *            预编译Statement。
	 * @param parameterIndex
	 *            jdbc参数索引。
	 * @throws SQLException
	 *             jdbc异常。
	 */
	private void setIntParameter(Object cellValue, PreparedStatement stmt, int parameterIndex) throws SQLException {
		if (cellValue instanceof Integer) {
			stmt.setInt(parameterIndex, (int) cellValue);
		} else {
			stmt.setInt(parameterIndex, Integer.parseInt((String) cellValue));
		}
	}

	/**
	 * 设置Long类型的参数。
	 * 
	 * @param cellValue
	 *            Excel单元格值。
	 * @param stmt
	 *            预编译Statement。
	 * @param parameterIndex
	 *            jdbc参数索引。
	 * @throws SQLException
	 *             jdbc异常。
	 */
	private void setLongParameter(Object cellValue, PreparedStatement stmt, int parameterIndex) throws SQLException {
		if (cellValue instanceof Long) {
			stmt.setLong(parameterIndex, (long) cellValue);
		} else {
			stmt.setLong(parameterIndex, Long.parseLong((String) cellValue));
		}
	}

	/**
	 * 设置Date类型的参数。
	 * 
	 * @param cellValue
	 *            Excel单元格值。
	 * @param stmt
	 *            预编译Statement。
	 * @param parameterIndex
	 *            jdbc参数索引。
	 * @throws SQLException
	 *             jdbc异常。
	 */
	private void setDateParameter(Object cellValue, PreparedStatement stmt, int parameterIndex) throws SQLException {
		if (cellValue instanceof Date) {
			stmt.setDate(parameterIndex, new java.sql.Date(((Date) cellValue).getTime()));
		} else {
			stmt.setDate(parameterIndex, new java.sql.Date(new Date().getTime())); // TODO
		}
	}

	/**
	 * 设置BigDecimal类型的参数。
	 * 
	 * @param cellValue
	 *            Excel单元格值。
	 * @param stmt
	 *            预编译Statement。
	 * @param parameterIndex
	 *            jdbc参数索引。
	 * @throws SQLException
	 *             jdbc异常。
	 */
	private void setBigDecimalParameter(Object cellValue, PreparedStatement stmt, int parameterIndex)
			throws SQLException {
		if (cellValue instanceof BigDecimal) {
			stmt.setBigDecimal(parameterIndex, (BigDecimal) cellValue);
		} else {
			stmt.setBigDecimal(parameterIndex, new BigDecimal((String) cellValue));
		}
	}

	/**
	 * 设置Double类型的参数。
	 * 
	 * @param cellValue
	 *            Excel单元格值。
	 * @param stmt
	 *            预编译Statement。
	 * @param parameterIndex
	 *            jdbc参数索引。
	 * @throws SQLException
	 *             jdbc异常。
	 */
	private void setDoubleParameter(Object cellValue, PreparedStatement stmt, int parameterIndex) throws SQLException {
		if (cellValue instanceof Double) {
			stmt.setDouble(parameterIndex, (double) cellValue);
		} else {
			stmt.setDouble(parameterIndex, Double.parseDouble((String) cellValue));
		}
	}

	/**
	 * 设置Float类型的参数。
	 * 
	 * @param cellValue
	 *            Excel单元格值。
	 * @param stmt
	 *            预编译Statement。
	 * @param parameterIndex
	 *            jdbc参数索引。
	 * @throws SQLException
	 *             jdbc异常。
	 */
	private void setFloatParameter(Object cellValue, PreparedStatement stmt, int parameterIndex) throws SQLException {
		if (cellValue instanceof Float) {
			stmt.setFloat(parameterIndex, (float) cellValue);
		} else {
			stmt.setFloat(parameterIndex, Float.parseFloat((String) cellValue));
		}
	}

}
